/*
 * Copyright 2017 requery.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.requery.ohos.sqlite;
import io.requery.ohos.DefaultMapping;
import io.requery.ohos.LoggingListener;
import io.requery.meta.EntityModel;
import io.requery.sql.Configuration;
import io.requery.sql.ConfigurationBuilder;
import io.requery.sql.Mapping;
import io.requery.sql.Platform;
import io.requery.sql.SchemaModifier;
import io.requery.sql.TableCreationMode;
import io.requery.sql.platform.SQLite;
import io.requery.util.function.Function;
import net.sqlcipher.utils.TextUtils;
import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.rdb.RdbOpenCallback;
import ohos.data.rdb.RdbStore;
import ohos.data.rdb.StoreConfig;
import ohos.data.resultset.ResultSet;
import ohos.data.resultset.ResultSetHook;
import org.jetbrains.annotations.Nullable;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLNonTransientConnectionException;

/**
 * Wrapper for working with ohos RDB databases. This provides a {@link Connection} wrapping
 * the standard java SQLite API turning it into standard JDBC APIs.
 *
 * <p> This class extends the standard {@link DatabaseHelper} and is used to create the database.
 * {#onCreate(RdbOpenCallback)} will create the database tables & columns automatically.
 *
 * <p> {#onUpgrade(RdbOpenCallback, int, int)} will create any missing tables/columns, however
 * it will not remove any tables/columns more complex upgrades should be handled by overriding
 * {#onUpgrade(RdbOpenCallback, int, int)} and implementing a script to handle the migration.
 *
 * @author Nikhil Purushe
 */
@SuppressWarnings("WeakerAccess")
public class DatabaseSource extends DatabaseHelper implements DatabaseProvider<RdbStore> {

    private static final String TAG = DatabaseSource.class.getName() ;
    private final Platform platform;
    private final EntityModel model;
    private Mapping mapping;
    private RdbStore db;
    private Configuration configuration;
    private boolean configured;
    private boolean loggingEnabled;
    private TableCreationMode mode;
    private Context dContext;
    private String dName;
    private int dVersion;
    private DatabaseHelper databaseHelper;
    private StoreConfig config;
    /**
     * Creates a new {@link DatabaseSource} instance.
     *
     * @param context context
     * @param model   the entity model
     * @param version the schema version
     */
    public DatabaseSource(Context context, EntityModel model, int version) {
        this(context, model, getDefaultDatabaseName(context, model), null, version);
    }

    /**
     * Creates a new {@link DatabaseSource} instance.
     *
     * @param context context
     * @param model   the entity model
     * @param name    database filename
     * @param version the schema version
     */
    public DatabaseSource(Context context, EntityModel model, @Nullable String name, int version) {
        this(context, model, name, null, version);
    }

    /**
     * Creates a new {@link DatabaseSource} instance.
     *
     * @param context context
     * @param model   the entity model
     * @param name    database filename
     * @param factory optional {CursorFactory}
     * @param version the schema version
     */
    public DatabaseSource(Context context,
                          EntityModel model,
                          @Nullable String name,
                          @Nullable ResultSetHook factory,
                          int version) {
        this(context, model, name, factory, version, new SQLite());
    }

    /**
     * Creates a new {@link DatabaseSource} instance.
     *
     * @param context context
     * @param model   the entity model
     * @param name    database filename
     * @param factory optional {CursorFactory}
     * @param version the schema version
     * @param platform platform instance
     */
    public DatabaseSource(Context context,
                          EntityModel model,
                          @Nullable String name,
                          @Nullable ResultSetHook factory,
                          int version,
                          SQLite platform) {
        //super(context, name, factory, version);
        super(context);
        if (model == null) {
            throw new IllegalArgumentException("null model");
        }
        this.platform = platform;
        this.model = model;
        this.mode = TableCreationMode.CREATE_NOT_EXISTS;
        this.dContext = context;
        this.dName = name;
        this.dVersion = version;
        this.databaseHelper = new DatabaseHelper(dContext);
        this.config= StoreConfig.newDefaultConfig(dName+".db");
    }

    @Override
    public void setLoggingEnabled(boolean enable) {
        this.loggingEnabled = enable;
    }

    @Override
    public void setTableCreationMode(TableCreationMode mode) {
        this.mode = mode;
    }

    private static String getDefaultDatabaseName(Context context, EntityModel model) {
        return TextUtils.isEmpty(model.getName()) ?
                context.getBundleName() : model.getName();
    }

    /**
     * Override to change the default {@link Mapping}.
     *
     * @param platform platform instance
     * @return the configured mapping.
     */
    protected Mapping onCreateMapping(Platform platform) {
        return new DefaultMapping(platform);
    }

    /**
     * Override to modify the configuration before it is built.
     *
     * @param builder {@link ConfigurationBuilder instance} to configure.
     */
    protected void onConfigure(ConfigurationBuilder builder) {
        if (loggingEnabled) {
            LoggingListener loggingListener = new LoggingListener();
            builder.addStatementListener(loggingListener);
        }
    }

    private Connection getConnection(RdbStore db) throws SQLException {
        synchronized (this) {
            if (!db.isOpen()) {
                throw new SQLNonTransientConnectionException();
            }
            return new SqliteConnection(db, dContext, dName, dVersion);
        }
    }

    @Override
    public Configuration getConfiguration() {
        if (mapping == null) {
            mapping = onCreateMapping(platform);
        }
        if (mapping == null) {
            throw new IllegalStateException();
        }
        if (configuration == null) {
            ConfigurationBuilder builder = new ConfigurationBuilder(this, model)
                .setMapping(mapping)
                .setPlatform(platform)
                .setBatchUpdateSize(1000);
            onConfigure(builder);
            configuration = builder.build();

        }
        return configuration;
    }

    @Override
    public void onCreate(RdbStore db) {

    }

    @Override
    public void onConfigure(RdbStore db) {

    }

    @Override
    public void onUpgrade(RdbStore db, int oldVersion, int newVersion) {
    }

    @Override
    public RdbStore getReadableDatabase() {
        return null;
    }

    @Override
    public RdbStore getWritableDatabase() {
        RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() {
            @Override
            public void onCreate(RdbStore rdbStore) {
                db = rdbStore;
                new SchemaModifier(getConfiguration()).createTables(TableCreationMode.CREATE);
            }

            @Override
            public void onUpgrade(RdbStore rdbStore, int i, int i1) {
                db = rdbStore;
                SchemaUpdater updater = new SchemaUpdater(getConfiguration(),
                        new Function<String, ResultSet>() {
                            @Override
                            public ResultSet apply(String s) {
                                return db.querySql(s, null);
                            }
                        }, mode);
                updater.update();
            }
        };
        if(db == null) {
            db = databaseHelper.getRdbStore(config, dVersion, rdbOpenCallback, null);
            }
        return db;
    }

    @Override
    public Connection getConnection() throws SQLException {
        synchronized (this) {

            if (db == null) {
                db = getWritableDatabase();
            }
            if (!configured ) {
                //db.executeSql("PRAGMA foreign_keys = ON");
                configured = true;
            }
            return getConnection(db);
        }
    }


    @Override
    public void close() throws Exception {
        db.close();
    }
}